package state

import (
	"encoding/json"
	"fmt"
	"github.com/cnfinder/svm/common"
	"github.com/cnfinder/svm/types"
	"github.com/cnfinder/svm/vm"
	"math/big"
	"os"
)




// 状态接口层 状态管理 ;
//AccountState 实现vm的StateDB的接口 用于进行测试 ,可以使用多种方式实现
type AccountState struct {
	Accounts map[common.Address]*accountObject `json:"accounts,omitempty"`

}


// 初始状态
func NewAccountStateDb() *AccountState {
	return &AccountState{
		Accounts:make(map[common.Address]*accountObject,0),
	}
}

var _ vm.StateDB = (*AccountState)(nil)



//====缺失方法 AccountState方法

// addr : 合约地址
func (accSt *AccountState) GetAccountObject(addr common.Address) *accountObject {
	return accSt.Accounts[addr]
}
func (accSt *AccountState) setAccountObject(accountObject *accountObject) {
	accSt.Accounts[accountObject.Address]=accountObject
}

func (accSt *AccountState) GetOrsetAccountObject(addr common.Address) *accountObject {

	if accSt.Accounts[addr]==nil{
		accSt.Accounts[addr]=newAccountObject(addr,accountData{
			Balance:big.NewInt(0),
			Nonce:0,
		})
	}

	return accSt.Accounts[addr]
}


//=============




// CreateAccount 创建账户接口
func (accSt *AccountState) CreateAccount(addr common.Address) {
	if accSt.GetAccountObject(addr) != nil {
		return
	}
	obj := newAccountObject(addr, accountData{Balance:big.NewInt(0),Nonce:0})
	accSt.setAccountObject(obj)
}


// SubBalance 减去某个账户的余额
func (accSt *AccountState) SubBalance(addr common.Address, amount *big.Int) {
	stateObject := accSt.GetOrsetAccountObject(addr)
	if stateObject != nil {
		stateObject.SubBalance(amount)
	}
}

// AddBalance 增加某个账户的余额
func (accSt *AccountState) AddBalance(addr common.Address, amount *big.Int) {
	stateObject := accSt.GetOrsetAccountObject(addr)
	if stateObject != nil {
		stateObject.AddBalance(amount)
	}
}

//// GetBalance 获取某个账户的余额
func (accSt *AccountState) GetBalance(addr common.Address) *big.Int {
	stateObject := accSt.GetOrsetAccountObject(addr)
	if stateObject != nil {
		return stateObject.Balance()
	}
	return new(big.Int).SetInt64(0)
}
//GetNonce 获取nonce
func (accSt *AccountState) GetNonce(addr common.Address) uint64 {
	stateObject := accSt.GetAccountObject(addr)
	if stateObject != nil {
		return stateObject.Nonce()
	}
	return 0
}

// SetNonce 设置nonce
func (accSt *AccountState) SetNonce(addr common.Address, nonce uint64) {
	stateObject := accSt.GetOrsetAccountObject(addr)
	if stateObject != nil {
		stateObject.SetNonce(nonce)
	}
}

// GetCodeHash 获取代码的hash值
func (accSt *AccountState) GetCodeHash(addr common.Address) common.Hash {
	stateObject := accSt.GetAccountObject(addr)
	if stateObject == nil {
		return common.Hash{}
	}
	return common.BytesToHash(stateObject.CodeHash())
}

//GetCode 获取智能合约的代码
func (accSt *AccountState) GetCode(addr common.Address) []byte {
	stateObject := accSt.GetAccountObject(addr)
	if stateObject != nil {
		return stateObject.Code()
	}
	return nil
}

//SetCode 设置智能合约的code
func (accSt *AccountState) SetCode(addr common.Address, code []byte) {
	stateObject := accSt.GetOrsetAccountObject(addr)
	if stateObject != nil {
		stateObject.SetCode(addr, code)
	}
}

// GetCodeSize 获取code的大小
func (accSt *AccountState) GetCodeSize(addr common.Address) int {
	stateObject := accSt.GetAccountObject(addr)
	if stateObject == nil {
		return 0
	}
	if stateObject.ByteCode != nil {
		return len(stateObject.ByteCode)
	}
	return 0
}

// AddRefund 暂时先忽略补偿
func (accSt *AccountState) AddRefund(uint64) {
	return
}

//GetRefund ...
func (accSt *AccountState) GetRefund() uint64 {
	return 0
}

// GetState 和SetState 是用于保存合约执行时 存储的变量是否发生变化 evm对变量存储的改变消耗的gas是有区别的
func (accSt *AccountState) GetState(addr common.Address, key common.Hash) common.Hash {
	stateObject := accSt.GetAccountObject(addr)
	if stateObject != nil {
		return stateObject.GetStorageState(&key)
	}
	return common.Hash{}
}

// SetState 设置变量的状态
func (accSt *AccountState) SetState(addr common.Address, key common.Hash, value common.Hash) {
	stateObject := accSt.GetOrsetAccountObject(addr)
	if stateObject != nil {
		fmt.Printf("SetState key: %x value: %s", key, new(big.Int).SetBytes(value[:]).String())
		stateObject.SetStorageState(key, value)
	}
}

// Suicide 暂时禁止自杀
func (accSt *AccountState) Suicide(common.Address) bool {
	return false
}

// HasSuicided ...
func (accSt *AccountState) HasSuicided(common.Address) bool {
	return false
}

// Exist 检查账户是否存在
func (accSt *AccountState) Exist(addr common.Address) bool {
	return accSt.GetAccountObject(addr) != nil
}

//Empty 是否是空账户
func (accSt *AccountState) Empty(addr common.Address) bool {
	so := accSt.GetAccountObject(addr)
	return so == nil || so.Empty()
}

// RevertToSnapshot ...
func (accSt *AccountState) RevertToSnapshot(int) {

}

// Snapshot ...
func (accSt *AccountState) Snapshot() int {
	return 0
}

// AddLog 添加事件触发日志
func (accSt *AccountState) AddLog(log *types.Log) {
	fmt.Printf("log: %v", log)
}

// AddPreimage
func (accSt *AccountState) AddPreimage(common.Hash, []byte) {

}

// ForEachStorage  暂时没发现vm调用这个接口
func (accSt *AccountState) ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) {

}



// Commit 进行持久存储 这里我们只将其简单的json话之后保存到本地磁盘中。
func (accSt *AccountState) Commit() error {
	// 将bincode写入文件
	file, err := os.Create("./account_sate.db")
	if err != nil {
		return err
	}
	err = json.NewEncoder(file).Encode(accSt)
	//fmt.Println("len(binCode): ", len(binCode), " code: ", binCode)
	// bufW := bufio.NewWriter(file)
	// bufW.Write(binCode)
	// // bufW.WriteByte('\n')
	// bufW.Flush()
	file.Close()
	return err
}

func tryLoadFromDisk() (*AccountState,error) {

	file, err := os.Open("./account_sate.db")
	if err != nil && os.IsNotExist(err) {
		return NewAccountStateDb(), nil
	}
	if err != nil {
		return nil, err
	}

	// stat, _ := file.Stat()
	// // buf := stat.Size()
	var accStat AccountState

	err = json.NewDecoder(file).Decode(&accStat)
	return &accStat, err
}

//TryLoadFromDisk  尝试从磁盘加载AccountState
func LoadState() (*AccountState, error) {
	return tryLoadFromDisk()
}

//外部接口
func (accState *AccountState) SaveState()  {
	accState.Commit()
}




func (accSt *AccountState) String() string{
	var res string
	if accSt.Accounts==nil{
		res=fmt.Sprintf("empty\n")
	}else{

		for accAddr,accObj:= range accSt.Accounts{

			accType:=0
			if accObj.IsContract(){
				accType=1
			}

			res+=	fmt.Sprintf("accAddr: %s, type: %d , balance:%d, nonce:%d\n",accAddr.String(),accType,accObj.Balance().Int64(),accObj.Nonce())
		}
	}
	return res
}
